/* Copyright (C) 2021 Rain */

/* This file is part of CFS. */

/* 
  CFS is free software: you can redistribute it and/or modify 
  it under the terms of the GNU General Public License as published by 
  the Free Software Foundation, either version 3 of the License, or 
  (at your option) and later version. 
*/

/*
  CFS is distributed in the hope that it will be useful, 
  but WITHOUT ANY WARRANTY; without even the implied warranty of 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
  GNU General Public License for more details. 
*/

/*
  You should have received a copy of the GNU General Public License 
   along with CFS. If not, see <https://www.gnu.org/licenses/>.  
*/




#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <string.h>
#include <errno.h>

#include <sys/fcntl.h>
#include <sys/types.h>
#include <sys/time.h>

#define __CFS
#include "cfs.h"

void dump_sb(int);
void dump_inode(struct inode *);
void dump_dir(int fd, struct inode *);

__bn_t bmap(int, __inode_t, __bn_t);


int main(int argc, const char **argv) {
	char *fname = (char *) argv[1];
	int fd;

	if (!fname) {
		printf("fatal: excepted one argument 'file name', but %d given. \n", argc - 1);
		printf("usage: %s device\n", argv[0]);

		return 1;
	}
	
	fd = open(fname, O_RDWR);

	if (fd < 0) {
		printf("fatal: failed when try to open %s: %s\n", fname, strerror(errno));
		return 1;
	}

	dump_sb(fd);

	close(fd);

	return 0;
}

void dump_sb(int fd) {
	lseek(fd, 512, SEEK_SET);
	read(fd, &sb, sizeof(struct superblock));

	if (sb.magic != MAGIC) {
		printf("fatal: bad magic number. \n");
		close(fd);

		exit(1);
	}

	printf("super block: \n");

	printf("\tmagic number: \t\t0x%08x (ok)\n", sb.magic);
	printf("\ttotal blocks: \t\t%10d\t0x%08x\n", sb.nblocks, sb.nblocks);
	printf("\ttotal inodes: \t\t%10d\t0x%08x\n", sb.ninodes, sb.ninodes);

	printf("\tbbm blocks count: \t%10d\t0x%08x\n", sb.nb_bbm, sb.nb_bbm);
	printf("\tibm blocks count: \t%10d\t0x%08x\n", sb.nb_ibm, sb.nb_ibm);
	printf("\tbim blocks count: \t%10d\t0x%08x\n", sb.nb_bim, sb.nb_bim);
	printf("\tinode blocks count: \t%10d\t0x%08x\n", sb.nb_inodes, sb.nb_inodes);

	printf("\tstart block of bbm: \t%10d\t0x%08x\n", sb.bbm_bn, sb.bbm_bn);
	printf("\tstart block of ibm: \t%10d\t0x%08x\n", sb.ibm_bn, sb.ibm_bn);
	printf("\tstart block of bim: \t%10d\t0x%08x\n", sb.bim_bn, sb.bim_bn);
	printf("\tstart block of inodes: \t%10d\t0x%08x\n", sb.inodes_bn, sb.inodes_bn);

	printf("\tfirst data block: \t%10d\t0x%08x\n", sb.first_data_block, sb.first_data_block);
	printf("\tnext free inode number: %10d\t0x%08x\n", sb.next_inode, sb.next_inode);

	dump_inode(&sb.root_ino);
	dump_dir(fd, &sb.root_ino);

	return;
}


void read_inode(int fd, __inode_t ino, struct inode *inode) {
	lseek(fd, (sb.inodes_bn << BLOCK_SHIFT) + ino * sizeof(struct inode), SEEK_SET);
	read(fd, inode, sizeof(struct inode));

	return;
}

void dump_inode(struct inode *inode) {
	printf("\ninode %10u: ", inode->inode);

	if (inode->inode == ROOT_INO) 
		printf("(root)\n");

	else 
		putc('\n', stdout);

	printf("\tparent inode number: \t%10u ", inode->pinode);

	if (inode->pinode == ROOT_INO)
		printf("(root)\n");

	else 
		putc('\n', stdout);


	printf("\taccess: \t\t%10d\t0x%08x\n", inode->access, inode->access);
	
	/* for access */
	if (inode->access & A_ROOTRD) 
		printf("\t\t\t\troot readable\t");

	else 
		printf("\t\t\t\troot unreadable\t");

	if (inode->access & A_ROOTWR) 
		printf("root writable\t");
	
	else 
		printf("root unwritable\t");

	putc('\n', stdout);

	if (inode->access & A_USERRD) 
		printf("\t\t\t\tuser readable\t");

	else 
		printf("\t\t\t\tuser unreadable\t");

	if (inode->access & A_USERWR) 
		printf("user writable\t");

	else 
		printf("user unwritable\t");

	putc('\n', stdout);

	if (inode->access & A_REGFILE) 
		printf("\t\t\t\tregular file\n");

	if (inode->access & A_DIR) 
		printf("\t\t\t\tdirectory\n");

	if (inode->access & A_LINK) 
		printf("\t\t\t\thard/soft link\n");

	if (inode->access & A_EXEC) 
		printf("\t\t\t\texecutable\n");

	printf("\ttotal blocks: \t\t%10u\t0x%08x\n", inode->blocks_nr, inode->blocks_nr);
	printf("\tup size: \t\t%10u\t0x%08x\n", inode->upsize, inode->upsize);

	printf("\ttotal file size: \t%10u\t", (inode->blocks_nr << BLOCK_SHIFT) + inode->upsize);
	printf("%d KB %d bytes\n", fsize(inode) >> 10, fsize(inode) % 1024);

	printf("\tcreate time: \t\t%10u\n", inode->ctime);
	printf("\tlast write time: \t%10u\n", inode->wtime);

	printf("\tuid: \t\t\t%10d\n", inode->uid);
	printf("\tgid: \t\t\t%10d\n", inode->gid);

	printf("\textend access: \t\t%10d\t0x%08x\n", inode->eaccess, inode->eaccess);

	return;
}



void dump_dir(int fd, struct inode *dir) {
	int i;
	struct dentry *entries = (struct dentry *) malloc(BLOCK_SIZE);

	printf("directory: \n");

	for (i = 0; i < dir->blocks_nr; i++) {
		lseek(fd, bmap(fd, dir->inode, i) << BLOCK_SHIFT, SEEK_SET);
		read(fd, entries, BLOCK_SIZE);

		int j;

		for (j = 0; j < BLOCK_SIZE / sizeof(struct dentry) && entries[j].ino; j++) {
			printf("\tinode %u: %s\n", entries[j].ino, entries[j].name);

			struct inode ino;

			read_inode(fd, entries[j].ino, &ino);
			dump_inode(&ino);

			if (ino.access & A_DIR) 
				dump_dir(fd, &ino);
		}
	}

	return;
}

__bn_t bmap(int fd, __inode_t ino, __bn_t bn) {
	__bn_t i;
	struct bim_entry *buf = (struct bim_entry *) malloc(BLOCK_SIZE);

	lseek(fd, sb.bim_bn << BLOCK_SHIFT, SEEK_SET);
	read(fd, buf, BLOCK_SIZE);

	/* search in buf */
	for (i = 0; i < BLOCK_SIZE / sizeof(struct bim_entry); i++) {
		if (buf[i].inode == ino  && buf[i].th_block == bn) 
			return i + sb.first_data_block;
	}

	return 0;
}
